#include <stdint.h>
#include <stdio.h>
#include "gd32f30x.h"
#include "delay.h"
#include "lcd_drv.h"
#include "RTT_Debug.h"

static void GpioInit(void)
{
	/* 使能RCU相关时钟 */
	rcu_periph_clock_enable(RCU_AF);  
	rcu_periph_clock_enable(RCU_GPIOA);  // 使能GPIOA的时钟
	rcu_periph_clock_enable(RCU_GPIOB);  // 使能GPIOB的时钟
	rcu_periph_clock_enable(RCU_GPIOC);  // 使能GPIOC的时钟

	/*
	*WR---PC0		RS---PC1  
	*CS---PC2		RD---PC3
	*RST--PC8		BK---PA8
	*D0--D15    	PB0--15
	*/
	
	/* 数据总线 */
	/* GPIO_SWJ_SWDPENABLE_REMAP: JTAG-DP disabled and SW-DP enabled */
	gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE); // JTAG重映射，PB3和PB4作为普通IO口使用
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_MAX, GPIO_PIN_ALL); // D0~D15
	gpio_port_write(GPIOB, 0); //  一组数据全部清零

	/* 控制信号 */
	gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_MAX, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_8);
	gpio_bit_set(GPIOC, GPIO_PIN_8);
	gpio_bit_reset(GPIOC, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);
	
	/* 背光信号 */
	gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_8);
	gpio_bit_reset(GPIOA, GPIO_PIN_8);

}

#define  LCD_RST_SET()   		gpio_bit_set(GPIOC, GPIO_PIN_8)
#define  LCD_RST_RESET()  		gpio_bit_reset(GPIOC, GPIO_PIN_8) 

#define  LCD_RD_SET()			gpio_bit_set(GPIOC, GPIO_PIN_3) 
#define  LCD_RD_RESET()			gpio_bit_reset(GPIOC, GPIO_PIN_3) 
	 
#define  LCD_CS_SET()    		gpio_bit_set(GPIOC, GPIO_PIN_2) 
#define  LCD_CS_RESET()			gpio_bit_reset(GPIOC, GPIO_PIN_2) 
				
#define  LCD_RS_SET()			gpio_bit_set(GPIOC, GPIO_PIN_1) 
#define  LCD_RS_RESET()			gpio_bit_reset(GPIOC, GPIO_PIN_1) 

#define  LCD_WR_SET()     		gpio_bit_set(GPIOC, GPIO_PIN_0) 
#define  LCD_WR_RESET()   		gpio_bit_reset(GPIOC, GPIO_PIN_0)

#define  LCD_BK_ON()      		gpio_bit_set(GPIOA, GPIO_PIN_8)
#define  LCD_BK_OFF() 	  		gpio_bit_reset(GPIOA, GPIO_PIN_8)
#define  LCD_BK_IS_ON()      	(SET == gpio_input_bit_get(GPIOA, GPIO_PIN_8))

#define  LCD_DATA_WRITE(data)   gpio_port_write(GPIOB, data) 
#define  LCD_DATA_READ()    	gpio_input_port_get(GPIOB)

/* __forceinline是编译器强制内联函数,在频繁调用的函数中使用 */
static __forceinline void LcdSendCmd(uint16_t cmdVal)  // 内联函数，和宏定义类似，没有函数调用的开销
{
	LCD_CS_RESET();
	LCD_RS_RESET();
    LCD_RD_SET();
	LCD_DATA_WRITE(cmdVal);
	LCD_WR_RESET();
	LCD_WR_SET();
	LCD_CS_SET();
}

static __forceinline void LcdSendData(uint16_t dataVal)
{
	LCD_CS_RESET();
	LCD_RS_SET();
    //LCD_RD_SET();
	LCD_DATA_WRITE(dataVal);
	LCD_WR_RESET();
	LCD_WR_SET();
	LCD_CS_SET();
}

static __forceinline uint16_t Lcd9327ReadReg(uint16_t addr)
{
	uint16_t data[6];
    
	LcdSendCmd(addr);
	/*SET GPIOB Input*/
	gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_MAX, GPIO_PIN_ALL);

    LCD_CS_RESET();
	LCD_RS_SET();
    for (uint8_t i = 0; i < 6; i++)
    {
        LCD_RD_RESET();
        LCD_RD_SET();
        data[i] = LCD_DATA_READ();
    }
    LCD_CS_SET();
	/*SET GPIOB Output*/
	gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_MAX, GPIO_PIN_ALL);

    return (data[3] << 8) | (data[4] & 0x00FF);
}

#define LCD_ILI9327_ID         		 0x9327

static void Lcd9327Init(void)
{
	LcdSendCmd(0xE9); 
	LcdSendData(0x20); 
    
	LcdSendCmd(0x11); // Exit Sleep 
	DelayNms(5);

	LcdSendCmd(0xD1); // VCOM Control
	LcdSendData(0x00); 
	LcdSendData(0x40); 
	LcdSendData(0x49); 

	LcdSendCmd(0xD0); // Power_Setting
	LcdSendData(0x07);
	LcdSendData(0x01); 
	LcdSendData(0x8A);
//	LcdSendCmd(0x21);      // 针对9327IPS反色屏，和之前的9327屏代码相比，只需要注释这个命令即可
	LcdSendCmd(0x36); // Set_address_mode
	LcdSendData(0x48);

	LcdSendCmd(0x3A); // Set_pixel_format
	LcdSendData(0x55); // 0x55:16bit/pixel,65k;0x66:18bit/pixel,262k;

	LcdSendCmd(0xC1);  // Display_Timing_Setting for Normal/Partial Mode
	LcdSendData(0x10); 
	LcdSendData(0x10); 
	LcdSendData(0x02); 
	LcdSendData(0x02); 

	LcdSendCmd(0xC0); // Set Default Gamma 
	LcdSendData(0x00); 
	LcdSendData(0x35); 
	LcdSendData(0x00); 
	LcdSendData(0x00); 
	LcdSendData(0x01); 
	LcdSendData(0x02); 

	LcdSendCmd(0xC5); // Set frame rate 
	LcdSendData(0x02); 

	//WriteComm(0x21);  //Enter_invert_mode

	LcdSendCmd(0xD2); // power setting 
	LcdSendData(0x01); 
	LcdSendData(0x22); 

	LcdSendCmd(0xC8); // Set Gamma 
	LcdSendData(0x02); 
	LcdSendData(0x77); 
	LcdSendData(0x77); 
	LcdSendData(0x00); 
	LcdSendData(0x00); 
	LcdSendData(0x08); 
	LcdSendData(0x00); 
	LcdSendData(0x00); 
	LcdSendData(0x57); 
	LcdSendData(0x00); 
	LcdSendData(0x08); 
	LcdSendData(0x00); 
	LcdSendData(0x08); 
	LcdSendData(0x80); 
	LcdSendData(0x00); 
	LcdSendCmd(0x29); // display on 
	DelayNms(100);
}

static void LcdInit(void)
{
	LCD_RST_RESET();
	DelayNms(100);
	LCD_RST_SET();
	DelayNms(100);
	uint16_t id;
	
	id = Lcd9327ReadReg(0xEF);	
	if (id != LCD_ILI9327_ID)
	{

		DBG_Error("LCD init error\n");
		return;
	}

	Lcd9327Init();
}

static void LcdSetCursor(LcdArea_t *area)
{
	LcdSendCmd(0x2A);
	LcdSendData(area->x1 >> 8);
	LcdSendData(area->x1 & 0xFF);
	LcdSendData(area->x2 >> 8);
	LcdSendData(area->x2 & 0xFF);
	LcdSendCmd(0x2B);
	LcdSendData(area->y1 >> 8);
	LcdSendData(area->y1 & 0xFF);
	LcdSendData(area->y2 >> 8);
	LcdSendData(area->y2 & 0xFF);
	LcdSendCmd(0x2C);
}

/* 在某一个区域显示一个颜色 */
void LcdFillPureColor(LcdArea_t *area, uint16_t color)
{ 
	
    if ((area->x1 > LCD_PIXEL_WIDTH - 1) || (area->x2 > LCD_PIXEL_WIDTH - 1)
		|| (area->y1 > LCD_PIXEL_HEIGHT - 1) || (area->y2 > LCD_PIXEL_HEIGHT - 1))
	{
		return;
	}

	LcdSetCursor(area);
	uint32_t totalPoint = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);
	LCD_CS_RESET();
	LCD_RD_SET();
	LCD_RS_SET();
	LCD_DATA_WRITE(color);
	for (uint32_t i = 0; i < totalPoint; i++) 
	{	
		LCD_WR_RESET();
		LCD_WR_SET();
	}
	LCD_CS_SET();
}

void LcdDrvInit(void)
{
	GpioInit();
	LcdInit();	
	LCD_BK_ON();
	LcdArea_t area = {0, 0, LCD_PIXEL_WIDTH - 1, LCD_PIXEL_HEIGHT - 1};
	LcdFillPureColor(&area, BLACK);
}

/**
***********************************************************
* @brief LCD显示ASCII字符
* @param uint16_t x, uint16_t y:显示起始位置坐标
* @param uint8_t ascii         :显示字符
* @param uint16_t textColor    :文字颜色
* @param uint16_t bkColor      :背景颜色
* @param AsciiFontDesc_t *font :使用的字库取模相关信息
* @return 
***********************************************************
*/
void LcdDrawChar(uint16_t x, uint16_t y, uint8_t ascii, uint16_t textColor, uint16_t bkColor, AsciiFontDesc_t *font)
{
	uint8_t *bitmap;
	LcdArea_t area;
	uint8_t data;
	uint16_t i, j, k;
	
	bitmap = (uint8_t *)font->bitmap;
	if (font->isJustNum)
	{
		bitmap += (ascii - '0') * font->charHasBytes;
	}
	else
	{
		bitmap += (ascii - ' ') * font->charHasBytes;
	}
	
	area.x1 = x;
	area.y1 = y;
	area.x2 = x + font->width - 1;
	area.y2 = y + font->height - 1;
	LcdSetCursor(&area);
	
	for (i = 0; i < font->height; i++)
	{
		for (j = 0; j < font->width / 8; j++)
		{
			data = *(bitmap++);
			for (k = 0; k < 8; k++)
			{
				if (data & 0x80)
				{
					LcdSendData(textColor);
				}
				else
				{
					LcdSendData(bkColor);
				}
				data <<= 1;
			}
		}
		
		if (font->width % 8 == 0)
		{
			continue;
		}
		
		data = *(bitmap++);
		for (j = 0; j < font->width % 8; j++)
		{
			if (data & 0x80)
			{
				LcdSendData(textColor);
			}
			else
			{
				LcdSendData(bkColor);
			}
			data <<= 1;
		}
	}
}

/**
***********************************************************
* @brief LCD显示单一颜色的图片
* @param uint16_t x, uint16_t y :显示起始位置坐标
* @param uint16_t textColor     :文字颜色
* @param uint16_t bkColor       :背景颜色
* @param ChineseFontDesc_t *font:汉字取模相关信息，对应某一固定汉字字串
* @return 
***********************************************************
*/
void LcdDrawChinese(uint16_t x, uint16_t y, uint16_t textColor, uint16_t bkColor, ChineseFontDesc_t *font)
{
	uint8_t *bitmap;
	LcdArea_t area;
	uint8_t data;
	uint16_t i, j, k, l;
	
	bitmap = (uint8_t *)font->bitmap;
	
	area.x1 = x;
	area.y1 = y;
//	area.x2 = x + (font->width * font->charNums) - 1;
	area.x2 = x + font->width - 1;
	area.y2 = y + font->height - 1;
	LcdSetCursor(&area);
	
	for (i = 0; i < font->charNums; i++)
	{
		for (j = 0; j < font->height; j++)
		{
			for (k = 0; k < font->width / 8; k++)
			{
				data = *(bitmap++);
				for (l = 0; l < 8; l++)
				{
					if (data & 0x80)
					{
						LcdSendData(textColor);
					}
					else
					{
						LcdSendData(bkColor);
					}
					data <<= 1;
				}
			}
			
			if (font->width % 8 == 0)
			{
				continue;
			}
			
			data = *(bitmap++);
			for (k = 0; k < font->width % 8; k++)
			{
				if (data & 0x80)
				{
					LcdSendData(textColor);
				}
				else
				{
					LcdSendData(bkColor);
				}
				data <<= 1;
			}
		}
		/*
			这个地方不能一次直接把位置直接写完,必须每一次都更新写的位置
		*/
		area.x1 = area.x2 + 1;
		area.x2 += font->width;
		LcdSetCursor(&area);
	}
}

/**
***********************************************************
* @brief LCD显示单一颜色的图片
* @param uint16_t x, uint16_t y:显示起始位置坐标
* @param uint16_t pictureColor :图片颜色
* @param uint16_t bkColor      :背景颜色
* @param PictureDesc_t *picDesc:使用的图片取模相关信息
* @return 
***********************************************************
*/
void LcdDrawPicture(uint16_t x, uint16_t y, uint16_t pictureColor, uint16_t bkColor, PictureDesc_t *picDesc)
{
	uint8_t *bitmap;
	LcdArea_t area;
	uint8_t data;
	uint16_t i, j, k;
	
	bitmap = (uint8_t *)picDesc->picture;
	area.x1 = x;
	area.y1 = y;
	area.x2 = x + picDesc->width - 1;
	area.y2 = y + picDesc->height - 1;
	LcdSetCursor(&area);
	
	for (i = 0; i < picDesc->height; i++)
	{
		for (j = 0; j < picDesc->width / 8; j++)
		{
			data = *(bitmap++);
			for (k = 0; k < 8; k++)
			{
				if (data & 0x80)
				{
					LcdSendData(pictureColor);
				}
				else
				{
					LcdSendData(bkColor);
				}
				data <<= 1;
			}
		}
		
		if (picDesc->width % 8 == 0)
		{
			continue;
		}
		
		data = *(bitmap++);
		for (j = 0; j < picDesc->width % 8; j++)
		{
			if (data & 0x80)
			{
				LcdSendData(pictureColor);
			}
			else
			{
				LcdSendData(bkColor);
			}
			data <<= 1;
		}
	}
}

/**
***********************************************************
* @brief LCD显示ASCII字符串
* @param uint16_t x, uint16_t y:显示起始位置坐标
* @param char *str             :显示字符串
* @param uint16_t textColor    :文字颜色
* @param uint16_t bkColor      :背景颜色
* @param AsciiFontDesc_t *font :使用的字库取模相关信息
* @return 
***********************************************************
*/
void LcdDrawString(uint16_t x, uint16_t y, char *str, uint16_t textColor, uint16_t bkColor, AsciiFontDesc_t *font)
{
	while (*str != '\0')
	{
		LcdDrawChar(x, y, *(str++), textColor, bkColor, font);
		x += font->width;
		if (x > 240)
		{
			x = 0;
			y += font->height;
		}
	}
}

void TurnOnScreen(void)
{
	LCD_BK_ON();
}

void TurnOffScreen(void)
{
	LCD_BK_OFF();
}

bool IsLcdBkOn(void)
{
	return LCD_BK_IS_ON() ;
}
